#include "ThreadPool.hpp"
#include "Thread.hpp"
#include "Task.hpp"
#include <unistd.h>
#include <memory>
int main()
{
    // ThreadPool<Task>* threadpool = new ThreadPool<Task>();
    // std::unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>());
    ThreadPool<Task>::getInstance()->run();// ThreadPool<Task>::getInstance():这个的返回值就是ThreadPool<T>* _tp的指针
    // tp->run();

    int x, y;
    char op;
    
    while (true)
    {
        std::cout << "请输入数据1# ";
        std::cin >> x;
        std::cout << "请输入数据2# ";
        std::cin >> y;
        std::cout << "请输入你要进行的运算符# ";
        std::cin >> op;
        Task t(x, y, op, mymath);
        std::cout << "你刚刚录入了一个任务：" << t.toTaskString() << " ,确定提交吗？ [y/n]# ";
        char comfirm;
        std::cin >> comfirm;
        if (comfirm == 'y')
            ThreadPool<Task>::getInstance()->push(t);
    }
    return 0;
}

///////////////////////////////////////////////线程池///////////////////////////////////////////////////////////

/*threadpool.h*/
/* 线程池:
* 一种线程使用模式。线程过多会带来调度开销，进而影响缓存局部性和整体性能。而线程池维护着多个线程，等待着
监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利
用，还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
* 线程池的应用场景：
* 1. 需要大量的线程来完成任务，且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务，使用线程池技
术是非常合适的。因为单个任务小，而任务数量巨大，你可以想象一个热门网站的点击次数。 但对于长时间的任务，比如一个
Telnet连接请求，线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
* 2. 对性能要求苛刻的应用，比如要求服务器迅速响应客户请求。
* 3. 接受突发性的大量请求，但不至于使服务器因此产生大量线程的应用。突发性大量客户请求，在没有线程池情
况下，将产生大量线程，虽然理论上大部分操作系统线程数目最大值不是问题，短时间内产生大量线程可能使内存到达极限，
出现错误.
* 线程池的种类：
* 线程池示例：
* 1. 创建固定数量线程池，循环从任务队列中获取任务对象，
* 2. 获取到任务对象后，执行任务对象中的任务接口
*/

////////////////////////////////////////////////单例模式//////////////////////////////////////////////////////////


// 10. 线程安全的单例模式
// 什么是单例模式

// 单例模式的特点
// 某些类, 只应该具有一个对象(实例), 就称之为单例.
// 例如一个男人只能有一个媳妇.
// 在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这
// 些数据.
// 饿汉实现方式和懒汉实现方式
// [洗完的例子]
// 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
// 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.
// 懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度.

// 饿汉方式实现单例模式
// template <typename T>
// class Singleton {
// static T data;
// public:
// static T* GetInstance() {
// return &data;
// }
// };
// 只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例.

// 懒汉方式实现单例模式
// template <typename T>
// class Singleton {
// static T* inst;
// public:
// static T* GetInstance() {
// if (inst == NULL) {
// inst = new T();
// }
// return inst;
// }
// };
// 存在一个严重的问题, 线程不安全.
// 第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.
// 但是后续再次调用, 就没有问题了.
// 懒汉方式实现单例模式(线程安全版本)
// // 懒汉模式, 线程安全
// template <typename T>
// class Singleton {
// volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
// static std::mutex lock;
// public:
// static T* GetInstance() {
// if (inst == NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.
// lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.
// if (inst == NULL) {
// inst = new T();
// }
// lock.unlock();
// }
// return inst;
// }
// };
// 注意事项:
// 1. 加锁解锁的位置
// 2. 双重 if 判定, 避免不必要的锁竞争
// 3. volatile关键字防止过度优化
// 11. STL,智能指针和线程安全


// STL中的容器是否是线程安全的?
// 不是.
// 原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.
// 而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).
// 因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全